<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <!-- 添加视口 -->
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>上下轮播 原生 js 实现 Touch 轮播图</title>
    <style>
        /* 清除标签默认边距 */
        body,
        ul,
        li,
        ol,
        img {
            margin: 0;
            padding: 0;
        }

        /* 清除 ul 等标签前面的“小圆点” */
        ul,
        li,
        ol {
            list-style-type: none;
        }

        /* 图片自适应 */
        img {
            width: 100%;
            /* height: auto; */
            aspect-ratio: 1/1;
            border: none;
            /* ie8 */
            display: block;
            -ms-interpolation-mode: bicubic;
            /*为了照顾ie图片缩放失真*/
        }

        /* 轮播图最外层盒子 */
        .carousel {
            position: relative;
            overflow: hidden;

            margin: 100px auto 0px;
            width: 60vw;
        }

        .carousel ul {
            /* 这个高度需要在JS里面动态添加 */
        }

        .carousel ul li {
            position: absolute;
            width: 100%;
            left: 0;
            top: 0;
            /* 使用 transform:translateY(300%) 暂时将 li 移动到屏幕外面去*/
            -webkit-transform: translateY(300%);
            transform: translateY(300%);
        }

        /* 小圆点盒子 */
        .carousel .points {
            /* 未知宽度的盒子，使用 absolute 定位，结合 transform 的方式进行居中 */
            position: absolute;
            top: 50%;
            right: 10px;
            transform: translateY(-50%);
            display: flex;
            flex-direction: column;
            align-items: center;
            row-gap: 5px;
        }

        /* 小圆点 */
        .carousel .points li {
            width: 5px;
            height: 5px;
            border-radius: 50%;
            border: 1px solid #fff;
            /* float: left; */
            margin: 0 2px;
        }

        /* 选中小圆点的样式类 */
        .carousel .points li.active {
            background-color: #fff;
        }
    </style>
</head>

<body>
    <section class="carousel">
        <ul>
            <li><img src="images/test-1.png" alt=""></li>
            <li><img src="images/test-2.png" alt=""></li>
            <li><img src="images/test-3.png" alt=""></li>
            <li><img src="images/test-4.png" alt=""></li>
            <li><img src="images/test-5.png" alt=""></li>
        </ul>
        <ol class="points"></ol>
    </section>
</body>


<script>
    window.onload = function () {
        var carousel = document.querySelector('.carousel');
        var carouselUl = carousel.querySelector('ul');
        var carouselList = carouselUl.querySelectorAll('li');
        var points = carousel.querySelector('ol');
        var timer = null;

        // 设置 ul 的高度
        carouselUl.style.height = carouselList[0].offsetHeight + 'px';
        // 屏幕的宽度(滑动的高度)
        var screenHeight = carouselList[0].offsetHeight;

        // 动态生成小圆点
        for (var i = 0; i < carouselList.length; i++) {
            var li = document.createElement('li');
            if (i == 0) {
                li.classList.add('active');
            }
            points.appendChild(li);
        }

        // 初始三个固定的位置
        var top = carouselList.length - 1; // 4
        var middle = 0;
        var bottom = 1;

        // 归位（多次使用，封装成函数）
        setTransform();

        // 调用定时器
        timer = setInterval(showNext, 2000);

        // 分别绑定touch事件
        var startY = 0; // 手指落点
        var startTime = null; // 开始触摸时间
        carouselUl.addEventListener('touchstart', touchstartHandler); // 滑动开始绑定的函数 touchstartHandler
        carouselUl.addEventListener('touchmove', touchmoveHandler); // 持续滑动绑定的函数 touchmoveHandler
        carouselUl.addEventListener('touchend', touchendHandeler); // 滑动结束绑定的函数 touchendHandeler

        // 轮播图片切换下一张
        function showNext() {
            // 轮转下标
            top = middle;
            middle = bottom;
            bottom++;
            //　极值判断
            if (bottom > carouselList.length - 1) {
                bottom = 0;
            }
            //添加过渡（多次使用，封装成函数）
            setTransition(1, 1, 0);
            // 归位
            setTransform();
            // 自动设置小圆点
            setPoint();
        }

        // 轮播图片切换上一张
        function showPrev() {
            // 轮转下标
            bottom = middle;
            middle = top;
            top--;
            //　极值判断
            if (top < 0) {
                top = carouselList.length - 1;
            }
            //添加过渡
            setTransition(0, 1, 1);
            // 归位
            setTransform();
            // 自动设置小圆点
            setPoint();
        }

        // 滑动开始
        function touchstartHandler(e) {
            // 清除定时器
            clearInterval(timer);
            // 记录滑动开始的时间
            startTime = Date.now();
            // 记录手指最开始的落点
            startY = e.changedTouches[0].clientY;
        }
        // 滑动持续中
        function touchmoveHandler(e) {
            // 获取差值 自带正负
            var dy = e.changedTouches[0].clientY - startY;
            // 干掉过渡
            setTransition(0, 0, 0);
            // 归位
            setTransform(dy);
        }
        //　滑动结束
        function touchendHandeler(e) {
            // 在手指松开的时候，要判断当前是否滑动成功
            var dy = e.changedTouches[0].clientY - startY;
            // 获取时间差
            var dTime = Date.now() - startTime;
            // 滑动成功的依据是滑动的距离（绝对值）超过屏幕的二分之一 或者滑动的时间小于300毫秒同时滑动的距离大于30
            if (Math.abs(dy) > screenHeight / 2 || (dTime < 300 && Math.abs(dy) > 30)) {
                // 滑动成功了
                // 判断用户是往哪个方向滑
                if (dy > 0) {
                    // 往右滑 看到上一张
                    showPrev();
                } else {
                    // 往左滑 看到下一张
                    showNext();
                }
            } else {
                // 添加上过渡
                setTransition(1, 1, 1);
                // 滑动失败了
                setTransform();
            }

            // 重新启动定时器
            clearInterval(timer);
            // 调用定时器
            timer = setInterval(showNext, 2000);
        }
        // 设置过渡
        function setTransition(a, b, c) {
            if (a) {
                carouselList[top].style.transition = 'transform 1s';
            } else {
                carouselList[top].style.transition = 'none';
            }
            if (b) {
                carouselList[middle].style.transition = 'transform 1s';
            } else {
                carouselList[middle].style.transition = 'none';
            }
            if (c) {
                carouselList[bottom].style.transition = 'transform 1s';
            } else {
                carouselList[bottom].style.transition = 'none';
            }
        }

        //　封装归位
        function setTransform(dy) {
            dy = dy || 0;
            carouselList[top].style.transform = 'translateY(' + (-screenHeight + dy) + 'px)';
            carouselList[middle].style.transform = 'translateY(' + dy + 'px)';
            carouselList[bottom].style.transform = 'translateY(' + (screenHeight + dy) + 'px)';
        }
        // 动态设置小圆点的active类
        var pointsList = points.querySelectorAll('li');

        function setPoint() {
            for (var i = 0; i < pointsList.length; i++) {
                pointsList[i].classList.remove('active');
            }
            pointsList[middle].classList.add('active');
        }
    }
</script>

</html>